#ifndef _CCSKELETALSPRITE_H_
#define _CCSKELETALSPRITE_H_

#include "cocos2d.h"

#include "CCSkeleton.h"
#include "CCSkin.h"
#include "CCSkeletalAnimation.h"
#include "CCSpineSprite.h"

#include <functional>

NS_CC_BEGIN

/**
 * sprite can play skeletal animation
 *
 * \note
 * by default, its content size is zero
 */

class CCSkeletalSprite : public CCNode {
    
public:
    enum blendType {
        tSwitch = 0,
        tFadeInOut
    };

protected:
	/// skeleton
	CCSkeleton* m_skeleton;
	
	/// current skeletal animation
	CCSkeletalAnimation* m_animation;
    
	/// loop flag, -1 for infinite loop, 0 for no loop, > 0 for loop times
	int m_loop;
	
	/// root bone
	CCBone* m_rootBone;
	
	/// current animation time
	float m_frameTime;
	
	/// animation if paused?
	bool m_paused;
    
    bool _isFlipX;
    
    GLubyte _opacity;
    
    vector<CCNode*> _rootSpriteList;
    
    //for blend
    int _preLoop;
    float _preFrameTime;
    CCSkeletalAnimation* _preAnimation;
    
    float _blendTime;
    float _blendPassTime;
    
    blendType _type;
    
    CCSkin* _skin;

protected:
	CCSkeletalSprite();
	
	/// initialization before an animation is firsttime run
	void setupFirstFrameState();
	
	/**
	 * set animation time
	 *
	 * @param time animation time, in second. if the time exceeds the max time of animation,
	 *		then depend on the \c m_loop flag
	 */
	void setFrame(CCSkeletalAnimation *animation, float time, float alpha, float deltaTime);
	
	/// create slot sprites
	void createSlotSprites();
    
    virtual void createSprites(CCSprite* sprite,bool &lastAdditive,bool &nowAdditive);
	
	/// clean cached bone state of this node
	void clearBoneStates(CCBone* bone);
	
	/// clean cached slot state of this node
	void cleanSlotStates();
	
	/// sync state to all node
	void syncStates(CCBone* bone);

public:
	virtual ~CCSkeletalSprite();
	
	/**
	 * create a skeletal sprite
	 *
	 * @param s skeleton model
	 * @return \link wySkeletalSprite wySkeletalSprite\endlinnk
	 */
    static CCSkeletalSprite* create(const char* key);
    static CCSkeletalSprite* create(CCSkeleton* s);
    
    void setFlipX(bool bFlipX);
    bool isFlipX() {
        return _isFlipX;
    }

    void setOpacity(GLubyte opacity);

    GLubyte getOpacity() {
        return _opacity;
    }
    
    virtual void setShaderProgram(CCGLProgram* pShaderProgram);
    
    //void setFlipY();
    
	/// @draw
    virtual void draw();

    /**
     * get bone position relative to this node position
     *
     * @param boneName bone name
     * @return bone position, or zero point if bone is not found
     */
    CCPoint getBonePosition(const char* boneName);
    
    /**
     * get bone position relative to world origin
     *
     * @param boneName bone name
     * @return bone position, or zero point if bone is not found
     */
    CCPoint getBonePositionRelativeToWorld(const char* boneName);
	
	/**
	 * stop any animation which is ongoing
	 *
	 * @param true means restore original state, or false means keep last frame state
	 */
	void stopAnimation(bool restore);
    
    bool animationIsDone() {
        return m_loop==0;
    }
    
    float getFrameTime() const {
        return m_frameTime;
    }
	void setFrameTime(float time) {
        m_frameTime = time;
    }
	
	/// tick animation
	void update(float delta,float deltaForBlend=-1);
    
    void updateAnim(CCSkeletalAnimation* animation,float &frameTime,int &loop,float delta,float alpha=1);
	
	/// set skeleton, this will stop current animation if any
	void setSkeleton(CCSkeleton* s);
	
	/// get skeleton
	CCSkeleton* getSkeleton() { return m_skeleton; }
	
	/// play an animation, that will stop current animation if any
	void playAnimation(CCSkeletalAnimation* anim,blendType type=tSwitch,float blendTime=0.0f);
	
	/**
	 * play an animation, that will stop current animation if any
	 *
	 * @param animName animation key in skeletal animation cache, the animation must be added
	 *		before calling this method. otherwise nothing will happen
	 */
	void playAnimation(const char* animName,blendType type=tSwitch,float blendTime=0.0f);
	
	/// get animation
	CCSkeletalAnimation* getAnimation() { return m_animation; }
    
    /// apply a skin to skeleton, if skin is not found, do nothing
    void applySkin(const char* skinName);
    
public:
    
    /**
     * set bone position relative its parent bone
     *
     * @param boneName bone name
     * @param x x position relative to parent bone
     * @param y y position relative to parent bone
     * @param fixed true means make position fixed so it won't be changed when playing
     *      animation. it is optional and default value is false
     */
    void setBonePosition(const char* boneName, float x, float y, bool fixed = false);
    
    /**
     * set bone rotation relative its parent bone
     *
     * @param boneName bone name
     * @param angle rotation degree, postive value is counter-clockwise
     * @param fixed true means make rotation fixed so it won't be changed when playing
     *      animation. it is optional and default value is false
     */
    void setBoneRotation(const char* boneName, float angle, bool fixed = false);
    
    /**
     * set bone scale relative its parent bone
     *
     * @param boneName bone name
     * @param scaleX x scale relative to parent bone
     * @param scaleY y scale relative to parent bone
     * @param fixed true means make scale fixed so it won't be changed when playing
     *      animation. it is optional and default value is false
     */
    void setBoneScale(const char* boneName, float scaleX, float scaleY, bool fixed = false);
    
    /**
     * set slot color
     *
     * @param slotName slot name
     * @param color color in argb format
     * @param fixed true means make color fixed so it won't be changed when playing
     *      animation. it is optional and default value is false
     */
    void setSlotColor(const char* slotName, const ccColor4B &color, bool fixed = false);
    
    /**
     * set slot color
     *
     * @param slotName slot name
     * @param attachmentName attachment name
     * @param fixed true means make attachment fixed so it won't be changed when playing
     *      animation. it is optional and default value is false
     */
    void setSlotAttachment(const char* slotName, const char* attachmentName, bool fixed = false);
	
	/// set loop style, -1 means infinite loop, 0 means no loop, larger than 0 means loop times
	void setLoopCount(int loop) { m_loop = loop; }
	
	/// get loop flag, -1 means infinite loop, 0 means no loop, larger than 0 means loop times
	int getLoopCount() { return m_loop; }
	
	/// pause animation
	void setPaused(bool flag) { m_paused = flag; }
    
	/// is paused?
	bool isPaused() { return m_paused; }
    
    CCSkin* getSkin() {
        return _skin;
    }
    
private:
    
    void cleanOldSkin();
    
    
private:
    bool _debugBone;
    bool _debugSlots;
    bool _debugBoundingBox;
    
    std::function<void(const SpineEvents*)> _eventsHandler;
    
public:
    
    void handleEvents(const SpineEvents* events) {
        _eventsHandler(events);
    }
    
    void registerEventsHandler(const std::function<void(const SpineEvents*)> &handler) {
        _eventsHandler=handler;
    }
    
    void setDebugBone(bool debugBone) {
        _debugBone=debugBone;
    }
    
    void setDebugSlots(bool debugSlots) {
        _debugSlots=debugSlots;
    }
    
    void setDebugBoundingBox(bool debugBoundingBox) {
        _debugBoundingBox=debugBoundingBox;
    }
    
    CCRect boundingBox();
    
    //bool clickTest(const CCPoint& pos);
    
    virtual void visit();
    
    //*********** unit only ****//
    
    void addBlendAnimation(const char* animName,float alpha=1);
    
    bool BlendAnimIsDone() const {
        return _blendAnimation==NULL;
    }
    
    void setPercentage(float p) {
        if(_hp) {
            _hp->setPercentage(p);
        }
    }

    void setHpVisible(bool visibleHp) {
        _visibleHp=visibleHp;
    }
    
private:
    
    void initUnit();
    
    void setUnit();
    
    CCSkeletalAnimation* _blendAnimation;
    float _blendAlpha;
    float _blendFrameTime;
    int _blendLoop;
    
    bool _visibleHp;
    
    CCSpineSprite* _hp;
    CCSpineSprite* _hpBg1;
    CCSpineSprite* _hpBg2;
    
    //*********** unit only ****//
};

NS_CC_END

#endif // __CCSkeletalSprite_h__
